Skip to content

Introduce a timing buffer for the Check for Updates flow to avoid jarring UI.#2879

Open
danielpunkass wants to merge 7 commits into
sparkle-project:2.xfrom
danielpunkass:buffer-progress-alert
Open

Introduce a timing buffer for the Check for Updates flow to avoid jarring UI.#2879
danielpunkass wants to merge 7 commits into
sparkle-project:2.xfrom
danielpunkass:buffer-progress-alert

Conversation

@danielpunkass
Copy link
Copy Markdown
Contributor

When a check completes or fails very quickly, the "Checking for Updates" window flashes briefly before being replaced by the next alert.

This PR introduces a buffering mechanism whereby SPUStandardUserDriver defers presenting the progress window for 0.3 seconds. If a result is obtained within that time frame, the progress window is never displayed.

When and if the checking window has actually appeared on screen, a minimum visible duration of 0.7 seconds must elapse before the panel is dismissed.

Fixes #2878

Misc Checklist

  • My change requires a documentation update on Sparkle's website repository
  • My change requires changes to generate_appcast, generate_keys, or sign_update

Testing

I tested and verified my change by using one or multiple of these methods:

  • Sparkle Test App
  • Unit Tests
  • My own app
  • Other (please specify)

To replicate/confirm the various states for this UI flow, you can edit the SUCheckingWindowShowDelay and SUCheckingWindowMinDisplayTime const variables in SPUStandardUserDriver.m.

macOS version tested: macOS 26.5 (25F71)

@danielpunkass danielpunkass force-pushed the buffer-progress-alert branch from 02fd0a4 to 9b81430 Compare May 21, 2026 18:45
…ring UI.

When a check completes or fails very quickly, the "Checking for Updates" window
flashes briefly before being replaced by the next alert.

This PR introduces a buffering mechanism whereby SPUStandardUserDriver defers
presenting the progress window for 0.3 seconds. If a result is obtained within
that time frame, the progress window is never displayed.

When and if the checking window has actually appeared on screen, a minimum
visible duration of 0.7 seconds must elapse before the panel is dismissed.
@danielpunkass danielpunkass force-pushed the buffer-progress-alert branch from 9b81430 to 7d78e7e Compare May 21, 2026 18:47
@zorgiepoo
Copy link
Copy Markdown
Member

zorgiepoo commented May 22, 2026

I do wonder a bit if this behavior should be UI driver agnostic or not. In other words, can we imagine any type of custom UI where it's beneficial to not have this sort of timing buffer logic?

For example sparkle-cli --check-immediately always prints "Checking for Updates...\n" immediately before "No new update available!\n" against a bundle that's up to date, but it'd likely be nice for it to have time-buffered logic too (so Checking for Updates is not printed if check is very fast).

Other custom UIs may use the same UI element (button, window, ..) for both checking and show update states.

On the other hand, some custom UI implementors may have done their own time buffering deferral and may have to think about compatibility.

@zorgiepoo
Copy link
Copy Markdown
Member

zorgiepoo commented May 22, 2026

Well I didn't have to go far to find an app with custom UI that a timing buffer deferral approach would not be suitable, so scratch that.

custom-progress.mov

Comment thread Sparkle/SPUStandardUserDriver.m
Comment thread Sparkle/SPUStandardUserDriver.m Outdated
_pendingPostCheckingCancellation = nil;
}

- (BOOL)_finishPendingPostCheckingTransition SPU_OBJC_DIRECT
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return value is unused from caller.

Copy link
Copy Markdown
Member

@zorgiepoo zorgiepoo May 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this method is only used in one place and probably shouldn't be a method on its own. It's not helpful to separate it out. Also _pendingPostCheckingCompletion probably is not needed to store as an ivar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by new approach.

Comment thread Sparkle/SPUStandardUserDriver.m Outdated
Comment thread Sparkle/SPUStandardUserDriver.m Outdated
@danielpunkass danielpunkass marked this pull request as draft May 28, 2026 13:07
…ndardUserDriver and into SUStatusController so that most of the same show/dismiss patterns can be applied, with the buffered timing happened automatically. Some special care was needed to ensure that edge case scenarios like when the user clicks "Cancel" after the underlying check had already completed, that it doesn't continue to show them the checking results.
@danielpunkass danielpunkass marked this pull request as ready for review June 1, 2026 16:40
@zorgiepoo zorgiepoo added this to the 2.10.0 milestone Jun 4, 2026
Comment thread Sparkle/SPUStandardUserDriver.m Outdated
@zorgiepoo
Copy link
Copy Markdown
Member

zorgiepoo commented Jun 5, 2026

I believe showUpdateInFocus needs to be able to show the status window immediately when _retryTerminatingApplication != nil. An edge case I can think of is when you "Install and Relaunch" (which closes the window) but the app cancels the quit request, and the user checks for updates again and can retry that request.

Similarly the status window controller is used from the Updater.app (installer progress agent). That is already on a delayed timing buffer (~0.7 seconds SUDisplayProgressTimeDelay). This change would increase it by another 0.3 seconds, but it shouldn't.

I will probably need to look at this PR multiple times.

@danielpunkass
Copy link
Copy Markdown
Contributor Author

I believe showUpdateInFocus needs to be able to show the status window immediately when _retryTerminatingApplication != nil. An edge case I can think of is when you "Install and Relaunch" (which closes the window) but the app cancels the quit request, and the user checks for updates again and can retry that request.

Similarly the status window controller is used from the Updater.app (installer progress agent). That is already on a delayed timing buffer (~0.7 seconds SUDisplayProgressTimeDelay). This change would increase it by another 0.3 seconds, but it shouldn't.

My comparative lack of understanding of Sparkle nuances is showing! Sorry about missing the subtleties. I'll take a closer look again.

I will probably need to look at this PR multiple times.

Understandable, and if you think the gain is not worth the effort I totally get it.

@danielpunkass
Copy link
Copy Markdown
Contributor Author

I had missed that the progress agent is imposing its own display delay, and it seems like the best solution would be if it can leverage the same internal buffering that SUStatusController would now have. Either the buffering delay/decay could be configurable, or the Updater.app could accept the default timing imposed by the SUStatusController

@zorgiepoo
Copy link
Copy Markdown
Member

The delay in the installation side is intertwined with IPC from Autoupdate between the host app and the progress agent, so I think it'd be less risky to have a way to invoke an immediate way of showing the window, which would be needed for the _retryTerminatingApplication != nil path as well.

@danielpunkass
Copy link
Copy Markdown
Contributor Author

Does it seem like parameterizing the delay constant would be suitable? Then any client who needs it immediately could set the delay to 0?

@zorgiepoo
Copy link
Copy Markdown
Member

Sounds logical.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Visual flicker/noise when progress panel only appears for a fraction of a second

2 participants